Introduce a Location class.

Akinori MUSHA 10 年 前
コミット
b25142e793
共有4 個のファイルを変更した143 個の追加43 個の削除を含む
  1. 8 19
      app/models/event.rb
  2. 82 0
      lib/location.rb
  3. 49 0
      spec/lib/location_spec.rb
  4. 4 24
      spec/models/event_spec.rb

+ 8 - 19
app/models/event.rb

@@ -1,3 +1,5 @@
1
+require 'location'
2
+
1 3
 # Events are how Huginn Agents communicate and log information about the world.  Events can be emitted and received by
2 4
 # Agents.  They contain a serialized `payload` of arbitrary JSON data, as well as optional `lat`, `lng`, and `expires_at`
3 5
 # fields.
@@ -33,10 +35,10 @@ class Event < ActiveRecord::Base
33 35
   }
34 36
 
35 37
   def location
36
-    @location ||= {
37
-      # lat and lng are BigDecimal, so convert them to Float
38
-      lat: (lat.to_f if lat),
39
-      lng: (lng.to_f if lng),
38
+    @location ||= Location.new(
39
+      # lat and lng are BigDecimal, but converted to Float by the Location class
40
+      lat: lat,
41
+      lng: lng,
40 42
       radius:
41 43
         begin
42 44
           h = payload[:horizontal_accuracy].presence
@@ -47,21 +49,8 @@ class Event < ActiveRecord::Base
47 49
             (h || v || payload[:accuracy]).to_f
48 50
           end
49 51
         end,
50
-      course:
51
-        begin
52
-          if (v = payload[:course].presence) &&
53
-             (v = v.to_f) >= 0
54
-            v
55
-          end
56
-        end,
57
-      speed:
58
-        begin
59
-          if (v = payload[:speed].presence) &&
60
-             (v = v.to_f) >= 0
61
-            v
62
-          end
63
-        end,
64
-    }
52
+      course: payload[:course],
53
+      speed: payload[:speed].presence)
65 54
   end
66 55
 
67 56
   # Emit this event again, as a new Event.

+ 82 - 0
lib/location.rb

@@ -0,0 +1,82 @@
1
+Location = Struct.new(:lat, :lng, :radius, :speed, :course)
2
+
3
+class Location
4
+  protected :[]=
5
+
6
+  def initialize(data = {})
7
+    super()
8
+
9
+    case data
10
+    when Array
11
+      raise ArgumentError, 'unsupported location data' unless data.size == 2
12
+      self.lat, self.lng = data
13
+    when Hash, Location
14
+      data.each { |key, value|
15
+        begin
16
+          __send__("#{key}=", value)
17
+        rescue NameError
18
+        end
19
+      }
20
+    else
21
+      raise ArgumentError, 'unsupported location data'
22
+    end
23
+
24
+    yield self if block_given?
25
+  end
26
+
27
+  def lat=(value)
28
+    self[:lat] = floatify(value) { |f|
29
+      if f.abs <= 90
30
+        f
31
+      else
32
+        raise ArgumentError, 'out of bounds'
33
+      end
34
+    }
35
+  end
36
+
37
+  def lng=(value)
38
+    self[:lng] = floatify(value) { |f|
39
+      if f.abs <= 180
40
+        f
41
+      else
42
+        raise ArgumentError, 'out of bounds'
43
+      end
44
+    }
45
+  end
46
+
47
+  def radius=(value)
48
+    self[:radius] = floatify(value) { |f| f if f >= 0 }
49
+  end
50
+
51
+  def speed=(value)
52
+    self[:speed] = floatify(value) { |f| f if f >= 0 }
53
+  end
54
+
55
+  def course=(value)
56
+    self[:course] = floatify(value) { |f| f if (0..360).cover?(f) }
57
+  end
58
+
59
+  def present?
60
+    lat && lng
61
+  end
62
+
63
+  def empty?
64
+    !present?
65
+  end
66
+
67
+  private
68
+
69
+  def floatify(value)
70
+    case value
71
+    when nil, ''
72
+      return nil
73
+    else
74
+      float = Float(value)
75
+      if block_given?
76
+        yield(float)
77
+      else
78
+        float
79
+      end
80
+    end
81
+  end
82
+end

+ 49 - 0
spec/lib/location_spec.rb

@@ -0,0 +1,49 @@
1
+require 'spec_helper'
2
+
3
+describe Location do
4
+  let(:location) {
5
+    Location.new(
6
+      lat: BigDecimal.new('2.0'),
7
+      lng: BigDecimal.new('3.0'),
8
+      radius: 300,
9
+      speed: 2,
10
+      course: 30)
11
+  }
12
+
13
+  it "converts values to Float" do
14
+    expect(location.lat).to equal 2.0
15
+    expect(location.lng).to equal 3.0
16
+    expect(location.radius).to equal 300.0
17
+    expect(location.speed).to equal 2.0
18
+    expect(location.course).to equal 30.0
19
+  end
20
+
21
+  it "provides hash-style access to its properties with both symbol and string keys" do
22
+    expect(location[:lat]).to equal 2.0
23
+    expect(location['lat']).to equal 2.0
24
+  end
25
+
26
+  it "does not allow hash-style assignment" do
27
+    expect {
28
+      location[:lat] = 2.0
29
+    }.to raise_error
30
+  end
31
+
32
+  it "ignores invalid values" do
33
+    location2 = Location.new(
34
+      lat: 2,
35
+      lng: 3,
36
+      radius: -1,
37
+      speed: -1,
38
+      course: -1)
39
+    expect(location2.radius).to be_nil
40
+    expect(location2.speed).to be_nil
41
+    expect(location2.course).to be_nil
42
+  end
43
+
44
+  it "considers a location empty if either latitude or longitude is missing" do
45
+    expect(Location.new.empty?).to be_truthy
46
+    expect(Location.new(lat: 2, radius: 1).present?).to be_falsy
47
+    expect(Location.new(lng: 3, radius: 1).present?).to be_falsy
48
+  end
49
+end

+ 4 - 24
spec/models/event_spec.rb

@@ -18,13 +18,12 @@ describe Event do
18 18
   describe "#location" do
19 19
     it "returns a default hash when an event does not have a location" do
20 20
       event = events(:bob_website_agent_event)
21
-      event.location.should == {
21
+      event.location.should == Location.new(
22 22
         lat: nil,
23 23
         lng: nil,
24 24
         radius: 0.0,
25 25
         speed: nil,
26
-        course: nil,
27
-      }
26
+        course: nil)
28 27
     end
29 28
 
30 29
     it "returns a hash containing location information" do
@@ -37,31 +36,12 @@ describe Event do
37 36
         course: 90.0,
38 37
       }
39 38
       event.save!
40
-      event.location.should == {
39
+      event.location.should == Location.new(
41 40
         lat: 2.0,
42 41
         lng: 3.0,
43 42
         radius: 0.0,
44 43
         speed: 0.5,
45
-        course: 90.0,
46
-      }
47
-    end
48
-
49
-    it "ignores invalid speed and course" do
50
-      event = events(:bob_website_agent_event)
51
-      event.lat = 2
52
-      event.lng = 3
53
-      event.payload = {
54
-        speed: -1,
55
-        course: -1,
56
-      }
57
-      event.save!
58
-      event.location.should == {
59
-        lat: 2.0,
60
-        lng: 3.0,
61
-        radius: 0.0,
62
-        speed: nil,
63
-        course: nil,
64
-      }
44
+        course: 90.0)
65 45
     end
66 46
   end
67 47